#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#if defined(OS_ANDROID)||defined(OS_LINUX_SOC)
#include <err.h>
#endif
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <sys/stat.h>
#include <libmscommon/mscommon.h>
#include <libmscommon/mstime.h>
#include <libmslog/mslog.h>
#include "msprotocol.h"
#define  FLAG "FILE"
typedef struct FILEContext {
	time_t timep_r;
	time_t timep_w;
	time_t timep_s;
	char *filename;	
} FILEContext;
static int file_close(URLContext *s);
static int file_open(URLContext *s)
{
	if(1==s->is_connected){
		return 0;
	}
	FILEContext * file_ctt=ms_null;
	ms_mallocDes_goto(fail, file_ctt, (sizeof(FILEContext)), "FILEContext",s->url);
	s->priv_data=file_ctt;
	
	if(ms_strncmp_seq(s->url,"file://")){
		file_ctt->filename=&s->url[strlen("file://")-1];
	}else{
		file_ctt->filename=s->url;
	}
	
	if(s->flags&FLAG_FILE_EMPTY){
		ms_remove(file_ctt->filename);
	}
	int flags=O_RDWR;
#if defined	(OS_WIN32) 
	flags|=O_BINARY;
#endif	
	if(!(s->flags&FLAG_FILE_NOCREATE)){
		flags|=O_CREAT;
	}
	if(s->flags&FLAG_FILE_APPEND){
		flags|=O_APPEND;
	}
	ms_verbose("[%s]flags:%d",file_ctt->filename,s->flags);
	s->fd =ms_open(file_ctt->filename,flags,0666);
	if( s->fd == (-1) ){
		ms_errGoto(fail, "file_open(%s) failed(%d,%s)",file_ctt->filename,errno,strerror(errno) );
	}
	s->is_connected=1;
	return s->fd ;
fail:
	if(s->fd>0){
		ms_close(s->fd);
		s->fd=-1;
	}
	s->is_connected=0;
	return -1;
}

static int file_check(URLContext *s, int mask)
{
	FILEContext * file_ctt=s->priv_data;
	int ret=0;
	if (ms_access(file_ctt->filename,F_OK) < 0){
		return -1;
	}else{
		ret |=MSFILE_CHECK_EXSIT;
	}
	if ( (mask&MSFILE_CHECK_READ)
		&& (ms_access(file_ctt->filename, R_OK) >= 0)	){
		ret |= MSFILE_CHECK_READ;
	}
	if ( (mask&MSFILE_CHECK_WRITE)
		&& (ms_access(file_ctt->filename, W_OK) >= 0)	){
		ret |= MSFILE_CHECK_WRITE;
	}
	return ret;
}

static int file_read(URLContext *s, unsigned char *buf, int size)
{
	if(0==s->is_connected){
		return -1;
	}
	FILEContext * file_ctt=s->priv_data;
	if(!(s->flags&FLAG_NOCHECKEXSIT)){
		if(ms_true==mstime_api_counterAsyncSec(&file_ctt->timep_r,3)){
			if (file_check(s,MSFILE_CHECK_EXSIT) < 0){
				file_close(s);
				ms_errRet(-1,"File(%s) no exist",s->url);
			}
		}	
	}
	int ret = ms_read(s->fd, buf, size);
	if( ret <0 ){
		ms_errRet(-1, "file_read failed(%d,%s)", errno,strerror(errno) );
	}
	return ret;
}

static int file_write(URLContext *s, unsigned char *buf, int size)
{
	if(0==s->is_connected){
		return -1;
	}
	FILEContext * file_ctt=s->priv_data;
	if(!(s->flags&FLAG_NOCHECKEXSIT)){
		if(ms_true==mstime_api_counterAsyncSec(&file_ctt->timep_w,3)){
			if (file_check(s,MSFILE_CHECK_EXSIT) < 0){
				file_close(s);
				ms_errRet(-1,"File(%s) no exist",s->url);
			}
		}
	}
	int ret = ms_write(s->fd, buf, size);
	if( ret <0 ){
		ms_error("file_write (size:%d)failed(%d,%s),url:%s",size, errno,strerror(errno),s->url );
	}
    return ret;
}

/* XXX: use llseek */
/*
pos: 1L
whence:
	<1>SEEK_SET:Beginning of file
	<2>SEEK_CUR:Current position of file pointer
	<3>SEEK_END:End of file
*/
static ms_s64 file_seek(URLContext *s, ms_s64 pos, ms_s32 whence)
{
	if(0==s->is_connected){
		return -1;
	}
	FILEContext * file_ctt=s->priv_data;
	if(!(s->flags&FLAG_NOCHECKEXSIT)){
		if(ms_true==mstime_api_counterAsyncSec(&file_ctt->timep_s,3)){
			if (file_check(s,MSFILE_CHECK_EXSIT) < 0){
				file_close(s);
				ms_errRet(-1,"File(%s) no exist",s->url);
			}
		}	
	}
	int64 ret;
	ret = ms_lseek(s->fd, pos, whence);
	if( ret == (-1) ){
		ms_error("file_seek failed(%d,%s)", errno,strerror(errno) );
	}
	return ret;
}

static int file_close(URLContext *s)
{
	FILEContext * file_ctt=s->priv_data;
	if(0==s->is_connected){
		return 0;
	}
	if(s->fd>0){
		ms_close(s->fd);
		s->fd=-1;
	}
	ms_deMalloc(file_ctt);
	s->is_connected=0;
	return 0;
}

//support use file_delete function
static int file_remove(URLContext *s)
{
	char *filename=s->url;
	//Before remove,should close the fd;
	file_close(s);
	
	int ret = unlink(filename);
	if (ret < 0){
		ret = rmdir(filename);
		if (ret < 0){
			ms_error("file_delete(%s)  failed(%d,%s)", filename, errno,strerror(errno) );
		}
	}
	return ret;
}
static int file_rename(URLContext *s)
{
	char *filename=s->url;
	int ret = ms_rename(filename,s->opt.file.newname);
	if( ret == (-1) ){
		ms_error("file_rename filename,newfilename(%s,%s),file_rename failed(%d,%s)", filename,s->opt.file.newname, errno,strerror(errno) );
	}
    return ret;
}
static int file_empty(URLContext *s)
{
	char *filename=s->url;
	if( file_remove(s)<0){
		ms_errGoto(failed ,"file_remove(%s) failed", filename );
	}

	if( file_open(s)<0){
		ms_errGoto(failed ,"file_open(%s) failed", filename );
	}
	return 0;
failed:
	return -1;
}
#define MAXNUM_RECORD	 8

ms_void file_api_record(ms_s08 index,ms_byte *buf, ms_s32 size)
{
	static ms_bool flag_open[MAXNUM_RECORD]={ms_false};
	static URLContext urlCtt[MAXNUM_RECORD];
	if(ms_false==flag_open[index]){
		ms_memset0(&urlCtt[index], ms_bufsize(URLContext));
		urlCtt[index].flags=FLAG_WRITE;
		ms_sprintfs(urlCtt[index].url, "/tmp/msrecord_%03d", index);
		file_open(&urlCtt[index]);
		flag_open[index]=ms_true;
	}
	file_write(&urlCtt[index], (unsigned char *)buf, size);
}
ms_void file_api_enRecord(ms_bool flag_enable,ms_s08 index,ms_byte *buf, ms_s32 size)
{
	if(ms_false==flag_enable){
		return;
	}
	file_api_record(index,buf,size);
}


URLProtocol ffurl_file_protocol = {
	.nameDes	= msptc_getPtcNameDes(msptc_file),
	.url_open	= file_open,
	.url_read	= file_read,
	.url_write	= file_write,
	.url_close	= file_close,
	.priv_func={
		.file.url_check	= file_check,	
		.file.url_seek	= file_seek,		
		.file.url_remove	= file_remove,
		.file.url_rename	= file_rename,
		.file.url_empty	= file_empty,
	},
	.flags=STREAM_FILE_VOD,
	.next	=NULL,
};
